/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.opensearch.dataprepper.expression.util;

import org.antlr.v4.runtime.tree.ParseTree;
import org.hamcrest.DiagnosingMatcher;

import static org.opensearch.dataprepper.expression.util.ContextMatcher.hasContext;
import static org.opensearch.dataprepper.expression.util.TerminalNodeMatcher.isTerminalNode;

/**
 * @since 1.3
 * <p>Wrapper for {@link ContextMatcher} to create a readable and concise syntax</p>
 * <p>
 *     <b>Example</b>
 *     <pre>
 *         assertThat(parseTree, isParseTree(Expression).withChildrenMatching(isTerminalNode())<br>
 *         assertThat(parseTree, isParseTree(Expression).containingTerminalNode()
 *     </pre>
 * </p>
 */
public class ContextMatcherFactory {

    /**
     * @since 1.3
     * <p>Shortcut for constructor matching Hamcrest standard.</p>
     * <p>
     *     <b>Syntax without isParseTree</b>
     *     <pre>
     *         assertThat(parseTree, hasContext(Expression, hasContext(ConditionalExpression, isTerminalNode())));
     *     </pre>
     *     <b>Syntax with isParseTree</b>
     *     <pre>
     *         assertThat(parseTree, isParseTree(Expression, ConditionalExpression).containingTerminalNode());
     *     </pre>
     * </p>
     *
     * @see ContextMatcher#hasContext(Class, DiagnosingMatcher[])
     *
     * @param types List of class types to generate a nested list of ContextMatcher calls
     * @return matcher factory
     */
    @SafeVarargs
    public static ContextMatcherFactory isParseTree(final Class<? extends ParseTree> ... types) {
        return new ContextMatcherFactory(types);
    }

    final Class<? extends ParseTree>[] types;

    @SafeVarargs
    private ContextMatcherFactory(final Class<? extends ParseTree> ... types) {
        this.types = types;
    }

    /**
     * @since 1.3
     * Creates matcher with child asserts applied to the lowest level hasContext(...) generated by constructor
     *
     * @see ContextMatcher#hasContext(Class, DiagnosingMatcher[])
     *
     * @param childrenMatchers Matchers to assert against child nodes
     * @return Hamcrest matcher
     */
    @SafeVarargs
    public final DiagnosingMatcher<ParseTree> withChildrenMatching(final DiagnosingMatcher<ParseTree> ... childrenMatchers) {

        final Class<? extends ParseTree> lastType = types[types.length - 1];
        DiagnosingMatcher<ParseTree> result = hasContext(lastType, childrenMatchers);

        for (int x = types.length - 2; x >= 0; x--) {
            result = hasContext(types[x], result);
        }

        return result;
    }

    /**
     * @since 1.3
     * Creates matcher that assert the lowest level hasContext(...) contains a terminal node
     *
     * @see ContextMatcher#hasContext(Class, DiagnosingMatcher[])
     *
     * @return Hamcrest matcher
     */
    public DiagnosingMatcher<ParseTree> containingTerminalNode() {
        return withChildrenMatching(isTerminalNode());
    }
}
